/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "fcvt.h"
#include <stdarg.h>
#include <string.h>

extern int putchar(int a);

#ifndef CONFIG_PRINT_IN_MASK

static int flt(char **str, char *end, double num, int size, int precision, char fmt, int flags);

#define is_digit(c) ((c) >= '0' && (c) <= '9')

static int skip_atoi(const char **s)
{
    int i = 0;
    while (is_digit(** s)) {
        i = i * 10 + *((* s)++) - '0';
    }
    return i;
}

__attribute__((weak))
int printchar(char **str, char *end, int c)
{
    if (str) {
        if (end && *str >= end) {
            return 0;
        }
        **str = c;
        ++(*str);
    } else {
        putchar(c);
    }

    return 1;
}

#define PAD_RIGHT 1
#define PAD_ZERO 2
#define SIGN    4               // Unsigned/signed long
#define SPACE   8               // Space if plus
#define LEFT    16              // Left justified
#define SPECIAL 32              // 0x
#define LARGE   64              // Use 'ABCDEF' instead of 'abcdef'
#define PLUS    128             // Show plus

static int prints(char **out, char *end, const char *string, int width, int pad)
{
    register int pc = 0, padchar = ' ';

    if (width > 0) {
        register int len = 0;
        register const char *ptr;
        for (ptr = string; *ptr; ++ptr) {
            ++len;
        }
        if (len >= width) {
            width = 0;
        } else {
            width -= len;
        }
        if (pad & PAD_ZERO) {
            padchar = '0';
        }
    }
    if (!(pad & PAD_RIGHT)) {
        for (; width > 0; --width) {
            if (printchar(out, end, padchar) == 0) {
                break;
            }
            ++pc;
        }
    }
    for (; *string ; ++string) {
        if (printchar(out, end, *string) == 0) {
            break;
        }
        ++pc;
    }
    for (; width > 0; --width) {
        if (printchar(out, end, padchar) == 0) {
            break;
        }
        ++pc;
    }

    return pc;
}

/* the following should be enough for 32 bit int */
#define PRINT_BUF_LEN 12

static int printi(char **out, char *end, int i, int b, int sg, int width, int pad, int letbase)
{
    char print_buf[PRINT_BUF_LEN];
    register char *s;
    register int t, neg = 0, pc = 0;
    register unsigned int u = i;

    if (i == 0) {
        print_buf[0] = '0';
        print_buf[1] = '\0';
        return prints(out, end, print_buf, width, pad);
    }

    if (sg && b == 10 && i < 0) {
        neg = 1;
        u = -i;
    }

    s = print_buf + PRINT_BUF_LEN - 1;
    *s = '\0';

    while (u) {
        t = u % b;
        if (t >= 10) {
            t += letbase - '0' - 10;
        }
        *--s = t + '0';
        u /= b;
    }

    if (neg) {
        if (width && (pad & PAD_ZERO)) {
            if (printchar(out, end, '-')) {
                ++pc;
                --width;
            }
        } else {
            *--s = '-';
        }
    }

    return pc + prints(out, end, s, width, pad);
}

int print(char **out, char *end, const char *format, va_list args)
{
    register int width, pad, precision;
    register int pc = 0;
    char scr[2];

    for (; *format != 0; ++format) {
        if (*format == '%') {
            ++format;
            precision = width = pad = 0;
            if (*format == '\0') {
                break;
            }
            if (*format == '%') {
                goto _out;
            }
            if (*format == '-') {
                ++format;
                pad = PAD_RIGHT;
            }
            while (*format == '0') {
                ++format;
                pad |= PAD_ZERO;
            }
            // Get field width
            width = - 1;
            if (is_digit(*format)) {
                width = skip_atoi(&format);
            }

            precision = - 1;
            if (*format == '.') {
                ++format;
                if (is_digit(* format)) {
                    precision = skip_atoi(& format);
                } else if (* format == '*') {
                    ++ format;
                    precision = va_arg(args, int);
                }
                if (precision < 0) {
                    precision = 0;
                }
            }

            if (*format == 's') {
                register char *s = (char *)va_arg(args, int);
                pc += prints(out, end, s ? s : "(null)", width, pad);
                continue;
            }
            if (*format == 'd') {
                pc += printi(out, end, va_arg(args, int), 10, 1, width, pad, 'a');
                continue;
            }
            if ((*format == 'l') && ((*(format + 1) == 'd'))) {
                pc += printi(out, end, va_arg(args, int), 10, 1, width, pad, 'a');
                ++format;
                continue;
            }
            if ((*format == 'l') && ((*(format + 1) == 'l')) && ((*(format + 2) == 'd'))) {
                pc += printi(out, end, va_arg(args, long long), 10, 1, width, pad, 'a');
                ++format;
                ++format;
                continue;
            }
            if (*format == 'p') {
                pc += printi(out, end, va_arg(args, int), 16, 0, width, pad, 'a');
                continue;
            }
            if (*format == 'x') {
                pc += printi(out, end, va_arg(args, int), 16, 0, width, pad, 'a');
                continue;
            }
            if ((*format == 'l') && ((*(format + 1) == 'x'))) {
                pc += printi(out, end, va_arg(args, int), 16, 0, width, pad, 'a');
                ++format;
                continue;
            }
            if (*format == 'X') {
                pc += printi(out, end, va_arg(args, int), 16, 0, width, pad, 'A');
                continue;
            }
            if ((*format == 'L') && ((*(format + 1) == 'X'))) {
                pc += printi(out, end, va_arg(args, int), 16, 0, width, pad, 'A');
                ++format;
                continue;
            }
            if (*format == 'u') {
                pc += printi(out, end, va_arg(args, int), 10, 0, width, pad, 'a');
                continue;
            }
            if ((*format == 'l') && ((*(format + 1) == 'u'))) {
                pc += printi(out, end, va_arg(args, int), 10, 0, width, pad, 'a');
                ++format;
                continue;
            }
            if ((*format == 'l') && ((*(format + 1) == 'l')) && ((*(format + 2) == 'u'))) {
                pc += printi(out, end, va_arg(args, unsigned long long), 10, 0, width, pad, 'a');
                ++format;
                ++format;
                continue;
            }
            if (*format == 'c') {
                /* char are converted to int then pushed on the stack */
                scr[0] = (char)va_arg(args, int);
                scr[1] = '\0';
                pc += prints(out, end, scr, width, pad);
                continue;
            }

#ifndef NOFLOAT
            if ((*format == 'E') || (*format == 'G') || (*format == 'e') || (*format == 'f') || (*format == 'g')) {
                pc += flt(out, end, va_arg(args, double), width, precision, *format, pad | SIGN);
                continue;
            }
#endif
        } else {
_out:
            pc += printchar(out, end, *format);
        }
    }
    if (out) {
        **out = '\0';
    }
    va_end(args);
    return pc;
}
#endif

#ifndef __LOG_ENABLE
__attribute__((weak)) int puts(const char *out)
{
    while (*out != '\0') {
        putchar(*out);
        out++;
    }

    return 0;
}

__attribute__((weak)) int printf(const char *format, ...)
{
    int ret = 0;
    va_list args;

    va_start(args, format);

    ret = print(NULL, 0, format, args);

    return ret;
}

void log_print(int level, const char *tag, const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    vprintf(format, ap);
    va_end(ap);
}

__attribute__((weak)) int dprintf(const char *format, ...)
{
    int ret = 0;
    va_list args;

    va_start(args, format);

    ret = print(NULL, 0, format, args);

    return ret;
}
#endif

__attribute__((weak)) int assert_printf(const char *format, ...)
{
    int ret = 0;
    va_list args;

    va_start(args, format);

    ret = print(NULL, 0, format, args);

    while (1) {
    }
    return ret;
}

int sprintf(char *out, const char *format, ...)
{
    va_list args;

    va_start(args, format);

    return print(&out, 0, format, args);
}

inline int  __attribute__((weak)) vprintf(const char *fmt, va_list va)
{
    int ret;

    ret =  print(0, 0, fmt, va);

    return ret;
}

int perror(const char *fmt, ...) // autualy not link this function
{
    return 0;
}

#ifndef NOFLOAT
static void cfltcvt(double value, char *buffer, char fmt, int precision)
{
    int decpt, sign, exp, pos;
    char *digits = NULL;
    char cvtbuf[CVTBUFSIZE + 1];
    int capexp = 0;
    int magnitude;

    if (fmt == 'G' || fmt == 'E') {
        capexp = 1;
        fmt += 'a' - 'A';
    }

    if (fmt == 'g') {
        digits = ecvtbuf(value, precision, & decpt, & sign, cvtbuf);
        magnitude = decpt - 1;
        if (magnitude < - 4  ||  magnitude > precision - 1) {
            fmt = 'e';
            precision -= 1;
        } else {
            fmt = 'f';
            precision -= decpt;
        }
    }

    if (fmt == 'e') {
        digits = ecvtbuf(value, precision + 1, & decpt, & sign, cvtbuf);

        if (sign) {
            * buffer ++ = '-';
        }
        * buffer ++ = * digits;
        if (precision > 0) {
            * buffer ++ = '.';
        }
        memcpy_s(buffer, precision, digits + 1, precision);
        buffer += precision;
        * buffer ++ = capexp ? 'E' : 'e';

        if (decpt == 0) {
            if (value == 0.0) {
                exp = 0;
            } else {
                exp = - 1;
            }
        } else {
            exp = decpt - 1;
        }

        if (exp < 0) {
            * buffer ++ = '-';
            exp = - exp;
        } else {
            * buffer ++ = '+';
        }

        buffer[2] = (exp % 10) + '0';
        exp = exp / 10;
        buffer[1] = (exp % 10) + '0';
        exp = exp / 10;
        buffer[0] = (exp % 10) + '0';
        buffer += 3;
    } else if (fmt == 'f') {
        digits = fcvtbuf(value, precision, & decpt, & sign, cvtbuf);
        if (sign) {
            * buffer ++ = '-';
        }
        if (* digits) {
            if (decpt <= 0) {
                * buffer ++ = '0';
                * buffer ++ = '.';
                for (pos = 0; pos < - decpt; pos ++) {
                    * buffer ++ = '0';
                }
                while (* digits) {
                    * buffer ++ = * digits ++;
                }
            } else {
                pos = 0;
                while (* digits) {
                    if (pos ++ == decpt) {
                        * buffer ++ = '.';
                    }
                    * buffer ++ = * digits ++;
                }
            }
        } else {
            * buffer ++ = '0';
            if (precision > 0) {
                * buffer ++ = '.';
                for (pos = 0; pos < precision; pos ++) {
                    * buffer ++ = '0';
                }
            }
        }
    }

    * buffer = '\0';
}

static void forcdecpt(char *buffer)
{
    while (* buffer) {
        if (* buffer == '.') {
            return;
        }
        if (* buffer == 'e' || * buffer == 'E') {
            break;
        }
        buffer ++;
    }

    if (* buffer) {
        int n = strlen(buffer);
        while (n > 0) {
            buffer[n + 1] = buffer[n];
            n --;
        }

        * buffer = '.';
    } else {
        * buffer ++ = '.';
        * buffer = '\0';
    }
}

static void cropzeros(char *buffer)
{
    char *stop;

    while (* buffer && * buffer != '.') {
        buffer ++;
    }
    if (* buffer ++) {
        while (* buffer && * buffer != 'e' && * buffer != 'E') {
            buffer ++;
        }
        stop = buffer --;
        while (* buffer == '0') {
            buffer --;
        }
        if (* buffer == '.') {
            buffer --;
        }
        while (*++ buffer = * stop ++);
    }
}

static int flt(char **str, char *end, double num, int size, int precision, char fmt, int flags)
{
    int pc = 0;
    char tmp[80];
    char c, sign;
    int n, i;
    // Left align means no zero padding
    if (flags & LEFT) {
        flags &= ~ PAD_ZERO;
    }

    // Determine padding and sign char
    c = (flags & PAD_ZERO) ? '0' : ' ';
    sign = 0;
    if (flags & SIGN) {
        if (num < 0.0) {
            sign = '-';
            num = - num;
            size --;
        } else if (flags & PLUS) {
            sign = '+';
            size --;
        } else if (flags & SPACE) {
            sign = ' ';
            size --;
        }
    }

    // Compute the precision value
    if (precision < 0) {
        precision = 6;    // Default precision: 6
    } else if (precision == 0 && fmt == 'g') {
        precision = 1;    // ANSI specified
    }

    // Convert floating point number to text
    cfltcvt(num, tmp, fmt, precision);

    //  puts(tmp) ;

    // '#' and precision == 0 means force a decimal point
    if ((flags & SPECIAL) && precision == 0) {
        forcdecpt(tmp);
    }

    // 'g' format means crop zero unless '#' given
    if (fmt == 'g' && !(flags & SPECIAL)) {
        cropzeros(tmp);
    }

    n = strlen(tmp);

    // Output number with alignment and padding
    size -= n;
    if (!(flags & (PAD_ZERO | LEFT))) while (size -- > 0) {
            pc += printchar(str, end, ' ');
        }
    if (sign) {
        pc += printchar(str, end, sign);
    }
    if (!(flags & LEFT)) while (size -- > 0) {
            pc += printchar(str, end, c);
        }
    for (i = 0; i < n; i ++) {
        pc += printchar(str, end, tmp[i]);
    }
    while (size -- > 0) {
        pc += printchar(str, end, ' ');
    }

    return pc;
}

#endif
